/*
 * Copyright (c) 2020 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "auth_conn.h"

#include <string.h>

#include "aes_gcm.h"
#include "auth_interface.h"
#include "os_adapter.h"
#include "securec.h"
#include "tcp_socket.h"

/**
 * @description: 基于TCP进行数据的发送
 * @param {int} fd 描述符
 * @param {const char} *buf 缓冲区
 * @param {int} offset 发送起始偏移量
 * @param {int} count 发送的长度
 * @param {int} timeout 超时
 * @return {int} 所发送的数据量
 */
int AuthConnSend(int fd, const char *buf, int offset, int count, int timeout)
{
    if ((buf == NULL) || (offset < 0) || (count <= 0) || (offset + count <= 0)) {
        return -1;
    }

    return TcpSendData(fd, buf + offset, count, timeout);
}

/**
 * @description: 基于TCP进行数据的接收
 * @param {int} fd 描述符
 * @param {const char} *buf 缓冲区
 * @param {int} offset 接收起始偏移量
 * @param {int} count 接收的长度
 * @param {int} timeout 超时
 * @return {int} 所接收的数据量
 */
int AuthConnRecv(int fd, char *buf, int offset, int count, int timeout)
{
    if ((buf == NULL) || (offset < 0) || (count <= 0) || (offset + count <= 0)) {
        return -1;
    }

    return TcpRecvData(fd, buf + offset, count, timeout);
}

/**
 * @description: 根据数据的类型（1-4、8-9）来判断数据内容是否是加密的
 * @param {int} module 需要进行判断的数据类型
 * @return {bool} 是否加密
 */
bool ModuleUseCipherText(int module)
{
    if (module >= MODULE_TRUST_ENGINE && module <= MODULE_HICHAIN_SYNC) {
        return false;
    }
    if (module >= MODULE_AUTH_CHANNEL && module <= MODULE_AUTH_MSG) {
        return false;
    }
    return true;
}

/**
 * @description: 把传入的数据进行Aes-Gcm加密后返回
 * @param {long long} seqNum 数据包序列号
 * @param {const char} *dataIn 待加密数据
 * @param {unsigned char} *data 返回的加密数据
 * @param {unsigned int} dataLen 返回的加密数据的长度
 * @param {int} *bufLen 待发送数据的长度？
 * @return {int} return 0
 */
static int GetEncryptTransData(long long seqNum, const char *dataIn, unsigned char *data, unsigned int dataLen,
    int *bufLen)
{
    //注：获得一个会话密钥
    SessionKey *skey = AuthGetNewSessionKey();
    if (skey == NULL) {
        return -1;
    }

    //注：把SessionKey中的index（会话号？）放入到data中
    if (memcpy_s(data, sizeof(int), &skey->index, sizeof(int)) != EOK) {
        return -1;
    }
    data += sizeof(int);

    AesGcmCipherKey cipherKey = {0};
    cipherKey.keybits = GCM_KEY_BITS_LEN_128;
    //注：把SessionKey的key赋值给AesGcmCipherKey的key
    int ret = memcpy_s(cipherKey.key, SESSION_KEY_LENGTH, skey->key, AUTH_SESSION_KEY_LEN);
    //注：随机产生IV：计数器CTR的初始值
    unsigned char* randomIv = GenerateRandomIv();
    if (randomIv == NULL) {
        return -1;
    }
    //注：把随机产生的IV赋值给AesGcmCipherKey的iv
    ret += memcpy_s(cipherKey.iv, IV_LEN, randomIv, IV_LEN);
    free(randomIv);
    //注：使用seqNum覆盖AesGcmCipherKey中的部分randomIv，使其更具有随机性（不确定）
    ret += memcpy_s(cipherKey.iv, sizeof(seqNum), &seqNum, sizeof(seqNum));
    if (ret != 0) {
        return -1;
    }

    //注：调用EncryptTransData，以为密钥，加密dataIn的内容，并与SessionKey中的index（会话号？）一起在data参数之中返回
    int cipherLen = EncryptTransData(&cipherKey, (const unsigned char*)dataIn, strlen(dataIn), data, dataLen);
    if (cipherLen <= 0) {
        return -1;
    }

    //注：加密数据长度+数据包首部长度+会话号长度
    *bufLen = cipherLen + PACKET_HEAD_SIZE + MESSAGE_INDEX_LEN;
    return 0;
}

/**
 * @description: 把消息打包成为可供发送的数据包
 * @param {int} module 数据类型
 * @param {int} flags 数据包的附加标志
 * @param {long long} seqNum 数据包序列号？（ack+1）
 * @param {const char} *str 源数据
 * @param {int} *bufLen 返回处理完成后的数据长度
 * @return {char*} 打包好的数据包
 */
unsigned char* AuthConnPackBytes(int module, int flags, long long seqNum, const char *str, int *bufLen)
{
    if ((str == NULL) || (bufLen == NULL)) {
        return NULL;
    }

    //注：判断该消息是否需要进行加密
    bool isCipherText = ModuleUseCipherText(module);
    //注：根据是否需要进行加密计算出待发送消息的长度
    int dataLen = isCipherText ? (strlen(str) + MESSAGE_ENCRYPT_OVER_HEAD_LEN) : strlen(str);
    //注：打包后的数据长度，据此来进行空间的申请
    int len = dataLen + PACKET_HEAD_SIZE;
    unsigned char *buf = (unsigned char *)calloc(1, sizeof(unsigned char) * len);
    if (buf == NULL) {
        return NULL;
    }

    unsigned char *data = buf;
    //注：数据包的幻数？用以确认该数据包是否符合
    int identifier = PKG_HEADER_IDENTIFIER;
    //注：把identifier、module、seqNum、flags、dataLen复制到data
    unsigned int ret = (unsigned int)memcpy_s(data, sizeof(int), &identifier, sizeof(int));
    data += sizeof(int);
    ret |= (unsigned int)memcpy_s(data, sizeof(int), &module, sizeof(int));
    data += sizeof(int);
    ret |= (unsigned int)memcpy_s(data, sizeof(long long), &seqNum, sizeof(long long));
    data += sizeof(long long);
    ret |= (unsigned int)memcpy_s(data, sizeof(int), &flags, sizeof(int));
    data += sizeof(int);
    ret |= (unsigned int)memcpy_s(data, sizeof(int), &dataLen, sizeof(int));
    data += sizeof(int);
    //注：通过不断地与运算来检查复制结果
    if (ret != 0) {
        free(buf);
        return NULL;
    }

    //注：根据加密需求，把数据内容打包进data中
    if (isCipherText) {
        if (GetEncryptTransData(seqNum, str, data, dataLen, bufLen) != 0) {
            free(buf);
            return NULL;
        }
    } else {
        if (memcpy_s(data, dataLen, str, dataLen) != EOK) {
            free(buf);
            return NULL;
        }
        *bufLen = len;
    }

    return buf;
}

/**
 * @description: 将打包好的数据包发送出去
 * @param {int} fd 描述符
 * @param {int} module 数据类型
 * @param {int} flags 数据包的附加标志
 * @param {long long} seq
 * @param {const char} *data
 * @return {*}
 */
int AuthConnPostBytes(int fd, int module, int flags, long long seq, const char *data)
{
    //注：检查参数是否非法，即需要发送的数据是否存在
    if (data == NULL) {
        return -1;
    }
    int bufLen = 0;
    //注：获取打包好的数据
    unsigned char *buf = AuthConnPackBytes(module, flags, seq, data, &bufLen);
    if (buf == NULL) {
        return -1;
    }
    //注：把数据包发送出去
    int ret = AuthConnSend(fd, (char *)buf, 0, bufLen, 0);
    free(buf);
    buf = NULL;

    if (ret != bufLen) {
        SOFTBUS_PRINT("[AUTH] AuthConnPostBytes send fail\n");
        return -1;
    }

    return 0;
}

/**
 * @description: 将传入的JSON类型消息转化为字符串并发送出去
 * @param {int} fd 描述符
 * @param {int} module 数据类型
 * @param {int} flags 数据包的附加标志
 * @param {long long} seqNum 数据包序列号？（ack+1）
 * @param {const cJSON} *msg 所发送的消息，需要为JSON格式的
 * @return {int}
 */
int AuthConnPostMessage(int fd, int module, int flags, long long seqNum, const cJSON *msg)
{
    if (msg == NULL) {
        return -1;
    }

    //注：获取未格式化的（不友好显示的）json格式的字符串
    char *msgStr = cJSON_PrintUnformatted(msg);
    if (msgStr == NULL) {
        return -1;
    }

    //注：将json类型的消息调用AuthConnPostBytes()发送出去
    int ret = AuthConnPostBytes(fd, module, flags, seqNum, msgStr);
    free(msgStr);
    msgStr = NULL;

    return ret;
}

